﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
using System.Collections.ObjectModel;

using SGCG.Helpers;

namespace SGCG.Model
{
    public class Circle : BorderedGraphicObject, IFillable
    {

        public Circle(GraphicObject parent) : base(parent) { }

        private int x;
        public int X
        {
            get { return x; }
            set
            {
                x = value;
                RaiseGraphicPropertyChanged("X");
            }
        }

        private int y;
        public int Y
        {
            get { return y; }
            set
            {
                y = value;
                RaiseGraphicPropertyChanged("Y");
            }
        }        
        
        private int radius;
        public int Radius
        {
            get { return radius; }
            set
            {
                radius = value;
                RaiseGraphicPropertyChanged("Radius");
            }
        }

        private bool ShouldBePainted(int count, int totalCount)
        {
            int distanciaEntreTracejados = (LineStyle == LineStyle.Pontilhado) ? DotInterspace : DashInterspace;
            int tamanhoTracejado = (LineStyle == LineStyle.Pontilhado) ? 1 : DashSize;

            List<bool> toPaint = AdaptativeDashing.ComputeToPaint(totalCount + tamanhoTracejado, distanciaEntreTracejados, tamanhoTracejado);
            toPaint = toPaint.GetRange(tamanhoTracejado / 2, totalCount);

            return toPaint[count];
        }

        public override void Draw()
        {
            DrawFill();
            if (LineStyle == LineStyle.Normal)
                Draw(true, (count) => true);
            else
            {
                int totalCount = Draw(false, (count) => true);
                Draw(true, (count) => ShouldBePainted(count, totalCount));
            }
        }

        private int Draw(bool forReal, Func<int, bool> shouldBePainted)
        {
            Recompute();
            IDrawingCanvas drawingCanvas = this.DrawingCanvas;
            int x0 = ComputedX;
            int y0 = ComputedY;
            int count = 0;

            int radius = Radius;
            double f;
            int x, y, ddF_x, ddF_y;

            // I'm sorry. :'(
            f = ((double)5)/4 - radius;
            x = 0;
            y = radius;
            ddF_x = 1;
            ddF_y = -2 * radius;
            while (x < y)
            {
                if (f > 0)
                {
                    --y;
                    ddF_y += 2;
                    f += ddF_y;
                }
                ++x;
                ddF_x += 2;
                f += ddF_x;
                if (forReal && shouldBePainted(count))
                    SetPixel(drawingCanvas, x0 + x, y0 + y);
                ++count;
            }
            ++x;
            while (0 < y)
            {
                if (f < 0)
                {
                    ++x;
                    ddF_x += 2;
                    f += ddF_x;
                }
                --y;
                ddF_y += 2;
                f += ddF_y;
                if (forReal && shouldBePainted(count))
                    SetPixel(drawingCanvas, x0 + x, y0 + y);
                ++count;
            }
            f = ((double)5)/4 - radius;
            x = radius;
            y = 0;
            ddF_x = 2 * radius;
            ddF_y = 1;
            while (-x < y)
            {
                if (f > 0)
                {
                    --x;
                    ddF_x -= 2;
                    f -= ddF_x;
                }
                --y;
                ddF_y += 2;
                f += ddF_y;
                if (forReal && shouldBePainted(count))
                    SetPixel(drawingCanvas, x0 + x, y0 + y);
                ++count;
            }
            --y;
            while (0 < x)
            {
                if (f <= 0)
                {
                    --y;
                    ddF_y += 2;
                    f += ddF_y;
                }
                --x;
                ddF_x -= 2;
                f -= ddF_x;
                if (forReal && shouldBePainted(count))
                    SetPixel(drawingCanvas, x0 + x, y0 + y);
                ++count;
            }
            f = ((double)5)/4 - radius;
            x = 0;
            y = -radius;
            ddF_x = -1;
            ddF_y = 2 * radius;
            while (y < x)
            {
                if (f > 0)
                {
                    ++y;
                    ddF_y -= 2;
                    f -= ddF_y;
                }
                --x;
                ddF_x -= 2;
                f -= ddF_x;
                if (forReal && shouldBePainted(count))
                    SetPixel(drawingCanvas, x0 + x, y0 + y);
                ++count;
            }
            --x;
            while (y < 0)
            {
                if (f <= 0)
                {
                    --x;
                    ddF_x -= 2;
                    f -= ddF_x;
                }
                ++y;
                ddF_y -= 2;
                f -= ddF_y;
                if (forReal && shouldBePainted(count))
                    SetPixel(drawingCanvas, x0 + x, y0 + y);
                ++count;
            }
            f = ((double)5) / 4 - radius;
            x = -radius;
            y = 0;
            ddF_x = -2 * radius;
            ddF_y = -1;
            while (x < -y)
            {
                if (f > 0)
                {
                    ++x;
                    ddF_x += 2;
                    f += ddF_x;
                }
                ++y;
                ddF_y -= 2;
                f -= ddF_y;
                if (forReal && shouldBePainted(count))
                    SetPixel(drawingCanvas, x0 + x, y0 + y);
                ++count;
            }
            ++y;
            while (x < 0)
            {
                if (f <= 0)
                {
                    ++y;
                    ddF_y -= 2;
                    f -= ddF_y;
                }
                ++x;
                ddF_x += 2;
                f += ddF_x;
                if (forReal && shouldBePainted(count))
                    SetPixel(drawingCanvas, x0 + x, y0 + y);
                ++count;
            }
            return count;
        }

        private void SetPixel(IDrawingCanvas drawingCanvas, int x, int y)
        {
            drawingCanvas.SetPixel(x, y, this.Color);

            if (this.LineThickness == Model.LineThickness.Dupla)
            {
                drawingCanvas.SetPixel(x + 1, y + 1, this.Color);
                drawingCanvas.SetPixel(x + 1, y, this.Color);
                drawingCanvas.SetPixel(x, y + 1, this.Color);
            }
        }

        protected void CopyProperties(Circle other)
        {
            base.CopyProperties(other);
            other.X = X;
            other.Y = Y;
            other.Radius = Radius;
            other.Fill = Fill;
            other.FillColor = FillColor;
        }

        public override GraphicObject CreateCopy()
        {
            Circle circle = new Circle(null);
            CopyProperties(circle);
            return circle;
        }

        public int ComputedX { get; private set; }
        public int ComputedY { get; private set; }

        private void Recompute()
        {
            Point p = TransformPoint(new Point(X, Y));
            ComputedX = (int)Math.Round(p.X);
            ComputedY = (int)Math.Round(p.Y);
        }

        public override void ConsolidateTransform(ITransform transform)
        {
            Point p = transform.Transform(new Point(X, Y));
            X = (int)Math.Round(p.X);
            Y = (int)Math.Round(p.Y);
        }

        private void DrawFill()
        {
            if (Fill != FillType.None)
            {
                FloodPainter painter = new FloodPainter(DrawingCanvas.GetWidth(), DrawingCanvas.GetHeight());
                GraphicObject newGraphic = this.CreateCopy();
                if (newGraphic is IHasBorder)
                    (newGraphic as IHasBorder).LineStyle = Model.LineStyle.Normal;
                if (newGraphic is IFillable)
                    (newGraphic as IFillable).Fill = FillType.None;
                newGraphic.Attach(painter.FakeCanvas);
                newGraphic.Draw();
                painter.DrawingCanvas = DrawingCanvas;
                painter.Texture = TextureFactory.CreateTexture(Fill, FillColor);
                Point gg = GetBarycenter();
                painter.Draw((int)Math.Round(gg.X), (int)Math.Round(gg.Y));
            }
        }

        private FillType fill;
        public FillType Fill
        {
            get { return fill; }
            set
            {
                fill = value;
                RaiseGraphicPropertyChanged("Fill");
            }
        }

        private Color fillColor;
        public Color FillColor
        {
            get { return fillColor; }
            set
            {
                fillColor = value;
                RaiseGraphicPropertyChanged("FillColor");
            }
        }

        protected Point GetBarycenter()
        {
            Recompute();
            return new Point(ComputedX, ComputedY);
        }

    }
}
